#region File Description
//-----------------------------------------------------------------------------
// LaunchedComponent.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


#endregion

namespace NinjaBra
{
    /// <summary>
    /// A game component that can be launched at a specified velocity. The component's movement will be affected by
    /// gravity. Components may be given an angular velocity which would cause them to spin while moving.
    /// </summary>
    class LaunchedComponent : AnimatedComponent
    {
        #region Fields/Properties/Events


        /// <summary>
        /// Angular velocity in radians per second.
        /// </summary>
        public float AngularVelocity { get; private set; }

        /// <summary>
        /// Velocity in pixels per second.
        /// </summary>
        public Vector2 Velocity { get; private set; }

        /// <summary>
        /// Acceleration in pixels per squared second.
        /// </summary>
        public Vector2 Acceleration { get; private set; }

        /// <summary>
        /// The component's rotation in radians.
        /// </summary>
        public float Rotation { get; private set; }

        /// <summary>
        /// Bounding box containing the component. The box takes the component's position and rotation into account,
        /// but is axis aligned.
        /// </summary>
        public override BoundingBox Bounds
        {
            get
            {
                return BoundingBox.CreateFromPoints(GetCurrentBoundCornerPositions());
            }
        }

        /// <summary>
        /// Component's center, which is also its position.
        /// </summary>
        public override Vector2 Center
        {
            get
            {
                return Position;
            }
        }

        bool isEventFired = true;

        /// <summary>
        /// The height, in pixels, which causes the <see cref="DroppedPastHeight"/> event to be fired. Setting this
        /// property to null will cause no events to be fired.
        /// </summary>
        public float? NotifyHeight { get; set; }

        /// <summary>
        /// An event which fires once the component drops (crosses while having a downwards velocity) past a specified
        /// height. The height can be determined using the <see cref="NotifyHeight"/> property. The event will fire
        /// once per call to <see cref="Launch"/>.
        /// </summary>
        public event EventHandler DroppedPastHeight;


        #endregion

        #region Initialization


        /// <summary>
        /// Creates a new instance of the launched game component.
        /// </summary>
        /// <param name="game">Associated game object.</param>
        /// <param name="gameScreen">Game screen where the component will be presented.</param>
        /// <param name="texture">Texture asset which represents the component.</param>
        public LaunchedComponent(Game game, GameplayScreen gameScreen, Texture2D texture)
            : base(game, gameScreen, texture)
        {
        }

        /// <summary>
        /// Creates a new instance of the launched game component.
        /// </summary>
        /// <param name="game">Associated game object.</param>
        /// <param name="gameScreen">Game screen where the component will be presented.</param>
        /// <param name="animation">Animation object which represents the component.</param>
        public LaunchedComponent(Game game, GameplayScreen gameScreen, Animation animation)
            : base(game, gameScreen, animation)
        {
        }


        #endregion

        #region Update


        /// <summary>
        /// Updates the component's position.
        /// </summary>
        /// <param name="gameTime">Game time information.</param>
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);

            float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;

            Velocity += Acceleration * elapsedSeconds;
            Position += Velocity * elapsedSeconds;

            // Check whether the event generated by falling past a certain point needs to be fired.
            if (!isEventFired && NotifyHeight.HasValue && Position.Y > NotifyHeight.Value && Velocity.Y > 0)
            {
                if (DroppedPastHeight != null)
                {
                    DroppedPastHeight(this, EventArgs.Empty);
                }

                isEventFired = true;
            }

            Rotation += AngularVelocity * elapsedSeconds;
        }


        #endregion

        #region Rendering


        /// <summary>
        /// Renders the component.
        /// </summary>
        /// <param name="gameTime">Game time information.</param>
        public override void Draw(GameTime gameTime)
        {
            spriteBatch.Begin();

            animation.Draw(spriteBatch, Position, Rotation, VisualCenter, 1, SpriteEffects.None, 0);

            spriteBatch.End();
        }


        #endregion

        #region Public Methods


        /// <summary>
        /// Launches the component from a specified position and at a specified speed.
        /// </summary>
        /// <param name="initialPosition">Initial position for the component.</param>
        /// <param name="initialVelocity">Initial velocity for the component in pixels per second.</param>
        /// <param name="acceleration">Component's acceleration in pixels per squared second, usually represents 
        /// gravity.</param>
        /// <param name="angularVelocity">Component's angular velocity in radians per second.</param>
        public void Launch(Vector2 initialPosition, Vector2 initialVelocity, Vector2 acceleration, 
            float angularVelocity)
        {
            Launch(initialPosition, initialVelocity, acceleration, 0, angularVelocity);
        }

        /// <summary>
        /// Launches the component from a specified position and at a specified speed.
        /// </summary>
        /// <param name="initialPosition">Initial position for the component.</param>
        /// <param name="initialVelocity">Initial velocity for the component in pixels per second.</param>
        /// <param name="acceleration">Component's acceleration in pixels per squared second, usually represents 
        /// gravity.</param>
        /// <param name="initialRotation">Initial rotation amount for the component.</param>
        /// <param name="angularVelocity">Component's angular velocity in radians per second.</param>
        public void Launch(Vector2 initialPosition, Vector2 initialVelocity, Vector2 acceleration, 
            float initialRotation, float angularVelocity)
        {
            Position = initialPosition;
            Velocity = initialVelocity;
            Rotation = initialRotation;
            this.Acceleration = acceleration;
            this.AngularVelocity = angularVelocity;
            isEventFired = false;
        }

        /// <summary>
        /// Returns an array containing the component's edges.
        /// </summary>
        /// <returns>An array containing the component's edges. The edges are ordered as follows: Top, right, bottom
        /// and left.</returns>
        public Line[] GetEdges()
        {
            Vector2[] corners = GetCurrentBoundCornerPositions2D();

            Line[] result = new Line[4];
            result[0] = new Line(corners[3], corners[2]);
            result[1] = new Line(corners[2], corners[1]);
            result[2] = new Line(corners[1], corners[0]);
            result[3] = new Line(corners[0], corners[3]);

            return result;
        }


        #endregion

        #region Non-Public Methods


        /// <summary>
        /// Returns the correct corner positions of the component's bounding box in light of its rotation. The corners
        /// are returned in the same order that <see cref="BoundingBox.GetCorners"/> would return the first four 
        /// corners of a bounding box.
        /// </summary>
        /// <returns></returns>
        protected Vector3[] GetCurrentBoundCornerPositions()
        {
            // our cube is flat, work on 4 of the 8 corners and return them
            Vector3[] result = new Vector3[4];

            // First get the corners centered around (0, 0)
            BoundingBox baseBounds = base.Bounds;            
            
            Vector3[] unrotatedCornersAroundCenter = new Vector3[4]
            {
                new Vector3(-baseBounds.Width() / 2, baseBounds.Height() / 2, 0),
                new Vector3(baseBounds.Width() / 2, baseBounds.Height() / 2, 0),
                new Vector3(baseBounds.Width() / 2, -baseBounds.Height() / 2, 0),
                new Vector3(-baseBounds.Width() / 2, -baseBounds.Height() / 2, 0)
            };

            // Rotate the corners around (0, 0)
            Matrix rotationMatrix = Matrix.CreateRotationZ(Rotation);
            Vector3.Transform(unrotatedCornersAroundCenter, 0, ref rotationMatrix, result, 0, 4);
            
            // Now move the corners to the position corresponding to the component's actual position
            Matrix translationMatrix = Matrix.CreateTranslation(new Vector3(Position, 0));
            Vector3.Transform(result, 0, ref translationMatrix, result, 0, 4);

            return result;
        }

        /// <summary>
        /// Returns the correct corner positions of the component's bounding box in light of its rotation.
        /// </summary>
        /// <returns></returns>
        protected Vector2[] GetCurrentBoundCornerPositions2D()
        {
            // our cube is flat, work on 4 of the 8 corners and return them
            Vector3[] result3D = GetCurrentBoundCornerPositions();
            Vector2[] result = new Vector2[result3D.Length];

            for (int i = 0; i < result3D.Length; i++)
            {
                result[i] = new Vector2(result3D[i].X, result3D[i].Y);
            }

            return result;
        }


        #endregion
    }
}
